﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using WcfFlashRemoting.ServiceModel.Channels;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace WcfFlashRemoting.ServiceModel.Dispatcher
{
    class AmfMultipleInvoker : IOperationInvoker
    {
        DispatchRuntime dispatchRuntime;
        public AmfMultipleInvoker(DispatchRuntime dispatchRuntime)
        {
            this.dispatchRuntime = dispatchRuntime;
        }

        private const string SuccessSuffix = "/onResult";
        private const string FaultSuffix = "/onStatus";

        #region IOperationInvoker Members

        public object[] AllocateInputs()
        {
            return new object[1];
        }

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            List<AmfMessage> results = new List<AmfMessage>();
            IEnumerable<AmfMessage> msgs = (inputs[0] as IEnumerable<AmfMessage>);
            foreach (AmfMessage msg in msgs)
            {
                DispatchOperation op;
                try
                {
                    op = dispatchRuntime.Operations[msg.AmfBody.Target.Split('.').Last()];
                }
                catch
                {
                    AmfMessage fault = BuildFault(msg, code: "REQUEST.UNKNOWN_METHOD", description: "Requested an unknown method.", type: "Request Unknown Method");
                    results.Add(fault);
                    continue;
                }
                {
                    AmfMessage result = PerformOperation(instance, msg, op);
                    results.Add(result);
                }
            }
            outputs = new object[0];
            return results;
        }

        private AmfMessage PerformOperation(object instance, AmfMessage msg, DispatchOperation op)
        {
            object[] inputs = op.Invoker.AllocateInputs();
            try
            {
                op.Formatter.DeserializeRequest(msg, inputs);
            }
            catch (Exception ex)
            {
                HandleErrors(ex, msg.Version, msg);
                AmfMessage fault = BuildFault(msg, code: "REQUEST.CONTRACT", description: "Request did not conform to contract.", type : "Request Contract");
                return fault;
            }
            object[] outputs;
            try
            {
                object returnValue = op.Invoker.Invoke(instance, inputs, out outputs);
                AmfMessage result = op.Formatter.SerializeReply(msg.Version, outputs, returnValue) as AmfMessage;
                result.AmfBody.Target = msg.AmfBody.Response + SuccessSuffix;
                return result;
            }
            catch (FaultException ex)
            {
                Type t = ex.GetType();
                while (t != typeof(FaultException) && t.GetGenericTypeDefinition() != typeof(FaultException<>))
                {
                    t = t.BaseType;
                }
                if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(FaultException<>))
                {
                    AmfMessage fault = new AmfMessage(msg.Version, new AmfObjects.AmfHeader[0], new AmfObjects.AmfBody(), msg.AmfVersion);
                    Type genType = t.GetGenericArguments()[0];
                    fault.AmfBody.Content = new AmfObjects.AmfFault()
                    {
                        code = ex.Code.ToString(),
                        level = "error",
                        description = ex.Reason.ToString(),
                        details = t.GetProperty("Detail").GetValue(ex, null),
                        type = (genType.GetCustomAttributes(typeof(DataContractAttribute), false)[0] as DataContractAttribute).Name ?? genType.Name
                    };
                    fault.AmfBody.Target = msg.AmfBody.Response + FaultSuffix;
                    return fault;
                }
                else
                {
                    AmfMessage fault = new AmfMessage(msg.Version, new AmfObjects.AmfHeader[0], new AmfObjects.AmfBody(), msg.AmfVersion);
                    fault.AmfBody.Content = new AmfObjects.AmfFault()
                    {
                        code = ex.Code.ToString(),
                        level = "error",
                        description = ex.Reason.ToString(),
                    };
                    fault.AmfBody.Target = msg.AmfBody.Response + FaultSuffix;
                    return fault;
                }
            }
            catch (Exception ex)
            {
                var providedFaultMessage = HandleErrors(ex, msg.Version, msg);
                bool includeExceptionMessage = dispatchRuntime.ChannelDispatcher.Host.Description.Behaviors
                    .OfType<System.ServiceModel.Description.ServiceDebugBehavior>()
                    .Any(b => b.IncludeExceptionDetailInFaults);
                AmfMessage fault = new AmfMessage(msg.Version, new AmfObjects.AmfHeader[0], new AmfObjects.AmfBody(), msg.AmfVersion);
                if (includeExceptionMessage)
                {
                    fault.AmfBody.Content = new AmfObjects.AmfFault()
                    {
                        code = "UNHANDLED-EXCEPTION",
                        level = "error",
                        description = "An unhandled exception occurred in the Service.  The following message is unavailable in release mode: " + ex.Message,
                        type = "Exception.  The following message is unavailable in release mode: " + ex.GetType().FullName
                    };
                }
                else
                {
                    fault.AmfBody.Content = new AmfObjects.AmfFault()
                    {
                        code = "UNHANDLED-EXCEPTION",
                        level = "error",
                        description = "An unhandled exception occurred in the Service.",
                        type = "Exception"
                    };
                }
                fault.AmfBody.Target = msg.AmfBody.Response + FaultSuffix;
                return fault;
            }
        }

        private AmfMessage BuildFault(AmfMessage msg, string code, string description, string type)
        {
            AmfMessage fault = new AmfMessage(msg.Version, new AmfObjects.AmfHeader[0], new AmfObjects.AmfBody(), msg.AmfVersion);
            fault.AmfBody.Content = new AmfObjects.AmfFault()
            {
                code = code,
                level = "error",
                description = description,
                details = null,
                type = type
            };
            fault.AmfBody.Target = msg.AmfBody.Response + FaultSuffix;
            return fault;
        }

        private System.ServiceModel.Channels.Message HandleErrors(Exception ex, System.ServiceModel.Channels.MessageVersion messageVersion, System.ServiceModel.Channels.Message msg)
        {
            System.ServiceModel.Channels.Message outFaultMessage = msg;
            foreach (var err in dispatchRuntime.ChannelDispatcher.ErrorHandlers)
            {
                err.ProvideFault(ex, msg.Version, ref outFaultMessage);
                if (err.HandleError(ex))
                {
                    break;
                }
            }
            return outFaultMessage;
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            throw new NotSupportedException();
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            throw new NotSupportedException();
        }

        public bool IsSynchronous
        {
            get { return true; }
        }

        #endregion
    }
}
